#include "ltdc_lib.h"

#define ABS(x) (((x) > 0) ? (x) : -(x))

static int fd;
static struct fb_var_screeninfo var;
static int screen_size;
unsigned long fb_buffer;


int ltdc_init(const char *file)
{
	void *temp;

	fd = open(file, O_RDWR);
	if(fd < 0)
		return -1;

	if (ioctl(fd, FBIOGET_VSCREENINFO, &var))
		return -1;
	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
	printf("LCD info: %d x %d, %dbpp, screen_size %d\n", var.xres, var.yres, var.bits_per_pixel, screen_size);

	temp = mmap(NULL, screen_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if(temp == MAP_FAILED)
	{
		close(fd);
		return -1;
	}

	fb_buffer = (unsigned long)temp;
	return 0;
}

int ltdc_deinit(void)
{
	return close(fd);
}

void ltdc_draw_point(int x, int y, uint32_t pixel)
{
	unsigned long fb_index;

	switch(var.bits_per_pixel)
	{
		case 32:
			fb_index = (y * var.xres + x) * 4;
			if(fb_index < screen_size)
				*(volatile uint32_t*)(fb_buffer + fb_index) = pixel;
			break;
		case 24:
			fb_index = (y * var.xres + x) * 3;
			if(fb_index < screen_size)
			{
				*(volatile uint8_t*)(fb_buffer + fb_index + 2) = (uint8_t)(pixel >> 16);
				*(volatile uint8_t*)(fb_buffer + fb_index + 1) = (uint8_t)(pixel >> 8);
				*(volatile uint8_t*)(fb_buffer + fb_index + 0) = (uint8_t)(pixel >> 0);
			}
			break;
		default:
			fb_index = (y * var.xres + x) * 2;
			if(fb_index < screen_size)
				*(volatile uint16_t*)(fb_buffer + fb_index) = (uint16_t)pixel;
			break;
	}
}

uint32_t ltdc_read_point(int x, int y)
{
	uint32_t pixel;
	unsigned long fb_index;

	switch(var.bits_per_pixel)
	{
		case 32:
			fb_index = (y * var.xres + x) * 4;
			if(fb_index < screen_size)
				pixel = *(volatile uint32_t*)(fb_buffer + fb_index);
			break;
		case 24:
			fb_index = (y * var.xres + x) * 3;
			if(fb_index < screen_size)
			{
				pixel = 0;
				pixel |= (uint32_t)(*(volatile uint8_t*)(fb_buffer + fb_index + 2)) << 16;
				pixel |= (uint32_t)(*(volatile uint8_t*)(fb_buffer + fb_index + 1)) << 8;
				pixel |= (uint32_t)(*(volatile uint8_t*)(fb_buffer + fb_index + 0)) << 0;
			}
			break;
		default:
			fb_index = (y * var.xres + x) * 2;
			if(fb_index < screen_size)
				pixel = *(volatile uint16_t*)(fb_buffer + fb_index);
			break;
	}
	
	return pixel;
}

void ltdc_clear(uint32_t pixel)
{
	int x, y;

	for(x=0; x<var.xres; x++)
	{
		for(y=0; y<var.yres; y++)
		{
			ltdc_draw_point(x, y, pixel);
		}
	}
}

void display_line(int x1, int y1, int x2, int y2, uint32_t pixel)
{
	int deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0, \
	yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0, \
	curpixel = 0;
	
	deltax = ABS(x2 - x1);			/* The difference between the x's */
	deltay = ABS(y2 - y1);			/* The difference between the y's */
	x = x1;							/* Start x off at the first pixel */
	y = y1;							/* Start y off at the first pixel */


	if (x2 >= x1)					/* The x-values are increasing */
	{
		xinc1 = 1;
		xinc2 = 1;
	}
	else							/* The x-values are decreasing */
	{
		xinc1 = -1;
		xinc2 = -1;
	}
	
	if (y2 >= y1)					/* The y-values are increasing */
	{
		yinc1 = 1;
		yinc2 = 1;
	}
	else							/* The y-values are decreasing */
	{
		yinc1 = -1;
		yinc2 = -1;
	}
	
	if (deltax >= deltay)			/* There is at least one x-value for every y-value */
	{
		xinc1 = 0;					/* Don't change the x when numerator >= denominator */
		yinc2 = 0;					/* Don't change the y for every iteration */
		den = deltax;
		num = deltax / 2;
		numadd = deltay;
		numpixels = deltax;			/* There are more x-values than y-values */
	}
	else							/* There is at least one y-value for every x-value */
	{
		xinc2 = 0;					/* Don't change the x for every iteration */
		yinc1 = 0;					/* Don't change the y when numerator >= denominator */
		den = deltay;
		num = deltay / 2;
		numadd = deltax;
		numpixels = deltay;			/* There are more y-values than x-values */
	}
	
	for(curpixel = 0; curpixel <= numpixels; curpixel++)
	{
		ltdc_draw_point( x, y, pixel); 
		num += numadd;				/* Increase the numerator by the top of the fraction */
		if (num >= den)				/* Check if numerator >= denominator */
		{
			num -= den;				/* Calculate the new numerator value */
			x += xinc1;				/* Change the x as appropriate */
			y += yinc1;				/* Change the y as appropriate */
		}
		x += xinc2;					/* Change the x as appropriate */
		y += yinc2;					/* Change the y as appropriate */
	}
}

void display_rect(int x, int y, int width, int high, uint32_t pixel)
{
	int w,h;

	for(h = 0; h < high; h++)
	{
		for(w = 0; w < width; w++)
		{
			ltdc_draw_point(x+w, y+h, pixel);
		}
	}
}